package com.taint.asm.core;

import com.taint.asm.model.ClassReference;
import com.taint.asm.model.InheritanceMap;
import com.taint.asm.model.MethodReference;
import com.taint.asm.service.DataFlowService;
import com.taint.asm.service.Inheritance;
import com.taint.asm.service.MethodCallService;
import com.taint.asm.service.SortService;
import org.apache.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;


import java.util.*;

public class ClassAnaylzer {

    static Logger logger = Logger.getLogger(ClassAnaylzer.class);

    private static Frame<SourceValue>[] frames;
    //所有方法内的调用
    private static final Map<MethodReference.Handle, Set<MethodReference.Handle>> methodCalls=new HashMap<>();

    public static List<ClassReference> discoveredClasses=new ArrayList<>();
    public static List<MethodReference> discoveredMethods=new ArrayList<>();
    public static Map<ClassReference.Handle,ClassReference> classMap=new HashMap<>();
    public static Map<MethodReference.Handle,MethodReference> methodMap=new HashMap<>();
    // 方法返回值与哪些参数有关
    private static final Map<MethodReference.Handle, Set<Integer>> dataFlow = new HashMap<>();

    // 类名->类资源
    private static final Map<String, String> classFileByName = new HashMap<>();

    public static void EntryClassAnaylzer(byte[] clazz, String canonicalName) {
        logger.info("discover class:" + canonicalName);

        //这里判断hash值
        //梳理class 和 method 关系
        DiscoveryClassVisitor dcv = new DiscoveryClassVisitor(discoveredClasses, discoveredMethods);
        //构建ClassReader
        ClassReader classReader = new ClassReader(clazz);

        classReader.accept(dcv, ClassReader.EXPAND_FRAMES);
        //System.out.println(discoveredMethods.size());

        //根据已有的方法和类得到继承关系  这个目前没看懂 后续继续维护

        InheritanceMap inheritanceMap=Inheritance.anyalzer(discoveredClasses,discoveredMethods,classMap,methodMap);

        //System.out.println(inheritanceMap.entrySet());

        // 得到方法中的方法调用 重要
        MethodCallService.start(clazz,methodCalls,canonicalName);

        // 对方法进行拓扑逆排序
        List<MethodReference.Handle> sortedMethods = SortService.start(methodCalls);

        //System.out.println(sortedMethods);

        // 分析方法返回值与哪些参数有关
        DataFlowService.start(inheritanceMap, sortedMethods, classFileByName, classMap, dataFlow);



    }








    private static void ShowGraph(String name, MethodNode target) {
        int methodIndex = 0;

        //指令大于0
        if (target.instructions.size() > 0) {

            for (int i = 0; i < target.instructions.size(); i++) {
                final int index = methodIndex;
                //抽象操作码节点
                AbstractInsnNode insn = target.instructions.get(i);
                if (insn instanceof MethodInsnNode) {
                    MethodInsnNode methodInsn = (MethodInsnNode) insn;
                    System.out.printf(
                            "    edge%d_%d [label=\"%s#%s%s\"];%n",
                            index, i, methodInsn.owner, methodInsn.name, methodInsn.desc);
                } else if (insn instanceof FieldInsnNode) {
                    FieldInsnNode fieldInsn = (FieldInsnNode) insn;
                    System.out.printf(
                            "    edge%d_%d [label=\"%s#%s (%s)\"];%n",
                            index, i, fieldInsn.owner, fieldInsn.name, fieldInsn.desc);
                } else if (insn instanceof VarInsnNode) {
                    VarInsnNode varInsn = (VarInsnNode) insn;
                    String insnName = getName(varInsn.getOpcode());
                    System.out.printf(
                            "    edge%d_%d [label=\"%s (%d)\"];%n", index, i, insnName, varInsn.var);
                } else {
                    System.out.printf("    edge%d_%d [label=\"%s\"];%n", index, i, insn.getClass());
                }
                if (true) {
                    Analyzer<BasicValue> analyzer =
                            new Analyzer<BasicValue>(
                                    new BasicInterpreter(Opcodes.ASM7) {
                                        @Override
                                        public BasicValue merge(final BasicValue value1, final BasicValue value2) {
                                            // if (!value1.equals(value2)) {
                                            //  System.err.printf("%s, %s%n", value1.getType(), value2.getType());

                                            // }
                                            return super.merge(value1, value2);
                                        }
                                    }) {
                                @Override
                                protected void newControlFlowEdge(final int insnIndex, final int successorIndex) {
                                    AbstractInsnNode insn = target.instructions.get(insnIndex);
                                    int opcode = insn.getOpcode();

                                    if (insn instanceof JumpInsnNode) {
                                        String label = "unknown (" + opcode + ")";
                                        switch (opcode) {
                                            case Opcodes.IFNULL:
                                                if (successorIndex == insnIndex + 1) {
                                                    label = "";
                                                } else {
                                                    label = "if null";
                                                }
                                                break;
                                            case Opcodes.IFNONNULL:
                                                if (successorIndex == insnIndex + 1) {
                                                    label = "";
                                                } else {
                                                    label = "if not null";
                                                }
                                                break;
                                            case Opcodes.GOTO:
                                                label = "";
                                                break;
                                        }
                                        System.out.printf(
                                                "    edge%d_%d -> edge%d_%d [label=\"%s\"];%n",
                                                index, insnIndex, index, successorIndex, label);
                                    } else {
                                        System.out.printf(
                                                "    edge%d_%d -> edge%d_%d;%n", index, insnIndex, index, successorIndex);
                                    }
                                }

                                @Override
                                protected boolean newControlFlowExceptionEdge(
                                        final int insnIndex, final int successorIndex) {
                                    System.out.printf(
                                            "    edge%d_%d -> edge%d_%d [color = \"red\"];%n",
                                            index, insnIndex, index, successorIndex);
                                    return true;
                                }
                            };
                    try {
                        analyzer.analyze(name, target);
                    } catch (AnalyzerException e) {
                        e.printStackTrace();
                    }
                    System.out.println("  }");
                }

                methodIndex++;
            }

        }


        //ControlFlowAnaylzer = new ControlFlowAnaylzer(new DataFlowInterpreter(ASM9));

        Analyzer controlFlowAnaylzer = new Analyzer(new SourceInterpreter());
        try {
            controlFlowAnaylzer.analyze(name, target);
        } catch (AnalyzerException e) {
            e.printStackTrace();
        }

    }


    private static String getName(int opcode) {
        switch (opcode) {
            case Opcodes.ALOAD:
                return "ALOAD";
            case Opcodes.ASTORE:
                return "ASTORE";
            case Opcodes.RETURN:
                return "RETURN";
            case Opcodes.ATHROW:
                return "ATHROW";
        }
        return "unknonw (" + opcode + ")";
    }
}
